package blingking.jfinal.orm.autoconfigure;

import blingking.jfinal.orm.bean.JFMappingBean;
import blingking.jfinal.orm.properties.JFinalORMProperties;
import blingking.jfinal.orm.util.JFMappingBeanUtil;
import blingking.jfinal.orm.xa.AtomikosDataSourceBeanBuilder;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.activerecord.generator.MetaBuilder;
import com.jfinal.plugin.activerecord.generator.TableMeta;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import javax.sql.XADataSource;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author King
 * @description
 * @Date 2020/3/23
 */
public abstract class AbstractDataSourceAutoConfiguration {

    @Autowired
    ApplicationContext applicationContext;

    @Autowired
    JFinalORMProperties jFinalORMProperties;

    protected static Set<JFMappingBean> allJFMappingBeans = new HashSet<>();
    protected static Set<Class> addedMappingClasses = new HashSet<>();
    protected static List<ActiveRecordPlugin> allARPs = new ArrayList<>();

    public abstract void init();

    protected void config(){
        Map<String, DataSource> dataSourceMap = getDataSources();
        if (dataSourceMap!=null&&dataSourceMap.size()>0){
            dataSourceMap.forEach((k, v) -> addARP(k, v, v));
            startARP();
        }
    }

    protected abstract Map<String, DataSource> getDataSources();


    protected void addARP(String dataSourceName, DataSource buildTableMetaDataSource, DataSource dataSource) {
        ActiveRecordPlugin arp = new ActiveRecordPlugin(dataSourceName, dataSource);
        arp.setShowSql(jFinalORMProperties.isShowSql());
        String modelScanPackage = jFinalORMProperties.getModelScanPackage();
        if (modelScanPackage != null) {
            String[] modelScanPackages = modelScanPackage.split(",");
            try {
                Set<JFMappingBean> jfMappingBeans = JFMappingBeanUtil.scanMappingBeans(modelScanPackages, jFinalORMProperties.isScanChild(), dataSourceName);
                allJFMappingBeans.addAll(jfMappingBeans);
                addMapping(arp, jfMappingBeans, buildTableMetaDataSource);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        allARPs.add(arp);
    }

    private void addMapping(ActiveRecordPlugin arp, Set<JFMappingBean> jfMappingBeans, DataSource buildTableMetaDataSource) {
        List<String> tableNames = getTableNames(buildTableMetaDataSource);
        for (JFMappingBean jfMappingBean : jfMappingBeans) {
            String tableName = jfMappingBean.getTableName();
            String dataSourceName = jfMappingBean.getDataSourceName();
            Class clazz = jfMappingBean.getClazz();
            if (!isExist(clazz) && dataSourceName.equals(arp.getConfig().getName())) {
                for (String t : tableNames) {
                    if (t.replaceAll("_", "").toLowerCase().equals(tableName.toLowerCase())) {
                        arp.addMapping(t, clazz);
                        addedMappingClasses.add(clazz);
                        break;
                    }
                }
            }

        }
    }

    protected boolean isExist(Class clazz) {
        return addedMappingClasses.stream().anyMatch(x -> x.getName().equals(clazz.getName()));
    }

    protected void startARP() {
        for (ActiveRecordPlugin arp : allARPs) {
            arp.start();
            registerBean(arp);
        }
    }

    private List<String> getTableNames(DataSource buildTableMetaDataSource) {
        List<TableMeta> tableMetas = buildTableMetas(buildTableMetaDataSource);
        return tableMetas.stream().map(t -> t.name).collect(Collectors.toList());
    }

    private List<TableMeta> buildTableMetas(DataSource buildTableMetaDataSource) {
        MetaBuilder metaBuilder = new MetaBuilder(buildTableMetaDataSource);
        List<TableMeta> tableMetas = new ArrayList<>();
        try {
            tableMetas = metaBuilder.build();
        } catch (Exception e) {
            System.out.println("build tableMeda fail");
            e.printStackTrace();
        }
        return tableMetas;
    }

    private static AtomicInteger acpCount = new AtomicInteger(1);

    private void registerBean(ActiveRecordPlugin activeRecordPlugin) {
        ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext;
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("activeRecordPlugin" + acpCount.getAndIncrement(), activeRecordPlugin);
    }

    protected Map<String, DataSource> initDataSources(Map<String, DataSource> dataSourceMap) {
        dataSourceMap.forEach((k, v) -> {
            if (v instanceof XADataSource) {
                AtomikosDataSourceBean atomikosDataSourceBean = AtomikosDataSourceBeanBuilder.generBean(k).build(v);
                dataSourceMap.put(k, atomikosDataSourceBean);
            }
        });
        return dataSourceMap;
    }
}
